/*
 * Copyright 2008 Zoundry LLC, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.zoundry.gaedt.core.yaml;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jvyaml.DefaultYAMLConfig;
import org.jvyaml.DefaultYAMLFactory;
import org.jvyaml.Emitter;
import org.jvyaml.Representer;
import org.jvyaml.RepresenterImpl;
import org.jvyaml.Resolver;
import org.jvyaml.Serializer;
import org.jvyaml.SerializerImpl;
import org.jvyaml.YAML;
import org.jvyaml.YAMLConfig;
import org.jvyaml.YAMLFactory;
import org.jvyaml.nodes.MappingNode;
import org.jvyaml.nodes.Node;

/**
 * Wraps the YAML Map data and provides a simple xpath-like syntax to
 * access/modify it.
 */
public class ZGaeYAMLModel
{
    /** The YAML data. */
    private Map<String, Object> mYamlData;

    /**
     * C'tor.
     */
    public ZGaeYAMLModel()
    {
        this(new LinkedHashMap<String, Object>());
    }

    /**
     * C'tor.
     *
     * @param aYamlData
     */
    public ZGaeYAMLModel(String aYamlData)
    {
        DefaultYAMLConfig config = new DefaultYAMLConfig();
        YAMLFactory factory = createYamlFactory();

        Reader reader = new StringReader(aYamlData);
        List loadedYaml = YAML.loadAll(reader, factory, config);
        Map yamlData = (Map) loadedYaml.iterator().next();
        setYamlData(yamlData);
    }

    /**
     * C'tor.
     *
     * @param aYamlData
     */
    public ZGaeYAMLModel(Map<String, Object> aYamlData)
    {
        setYamlData(aYamlData);
    }

    /**
     * Serializes the data into YAML format.
     */
    public String serialize()
    {
        DefaultYAMLConfig config = new DefaultYAMLConfig();
        config.useHeader(false);
        config.useVersion(false);
        config.explicitStart(false);
        config.explicitTypes(false);
        config.canonical(false);
        DefaultYAMLFactory factory = createYamlFactory();
        String dump = YAML.dump(getYamlData(), factory, config);
        return dump;
    }

    /**
     * Query the YAML data using a pseudo xpath syntax. Returns null if no match
     * is found.
     *
     * TODO (yaml) Need to support a rich query syntax, specifically a position predicate for List entries
     *
     * @param aYamlQuery
     */
    public Object query(String aYamlQuery)
    {
        Map currentNodeMap = getYamlData();
        Object returnValue = null;

        // Example: /handlers[1]/url
        String[] steps = aYamlQuery.split("/");
        for (String step : steps)
        {
            if ("".equals(step))
                continue;

            // If we hit a step but have run out of node maps, then
            // our selection is null.
            if (currentNodeMap == null)
            {
                returnValue = null;
                break;
            }

            returnValue = currentNodeMap.get(step);
            if (returnValue == null)
                break;
            else if (returnValue instanceof Map)
                currentNodeMap = (Map) returnValue;
            else
                currentNodeMap = null;
        }

        return returnValue;
    }

    /**
     * Puts a single value into the yaml map.
     *
     * @param aKey
     * @param aValue
     */
    public void put(String aKey, Object aValue)
    {
        if (aValue == null || "".equals(aValue))
            getYamlData().remove(aKey);
        else
            getYamlData().put(aKey, aValue);
    }

    /**
     * Creates the factory to use when serializing/deserializing YAML.
     */
    protected DefaultYAMLFactory createYamlFactory()
    {
        return new DefaultYAMLFactory()
        {
            /**
             * @see org.jvyaml.DefaultYAMLFactory#createSerializer(org.jvyaml.Emitter,
             *      org.jvyaml.Resolver, org.jvyaml.YAMLConfig)
             */
            @Override
            public Serializer createSerializer(Emitter aEmitter, Resolver aResolver, YAMLConfig aCfg)
            {
                return new SerializerImpl(aEmitter, aResolver, aCfg)
                {
                    /**
                     * @see org.jvyaml.SerializerImpl#ignoreAnchor(org.jvyaml.nodes.Node)
                     */
                    @Override
                    protected boolean ignoreAnchor(Node aNode)
                    {
                        return true;
                    }
                };
            }

            /**
             * @see org.jvyaml.DefaultYAMLFactory#createRepresenter(org.jvyaml.Serializer,
             *      org.jvyaml.YAMLConfig)
             */
            @Override
            public Representer createRepresenter(Serializer aSerializer, YAMLConfig aCfg)
            {
                return new RepresenterImpl(aSerializer, aCfg)
                {
                    /**
                     * Overridden to create a linked hash map so that ordering of
                     * entries in the map (if any) is honored.
                     *
                     * @see org.jvyaml.RepresenterImpl#representMapping(java.lang.String,
                     *      java.util.Map, boolean)
                     */
                    @Override
                    public Node representMapping(String aTag, Map aMapping, boolean aFlowStyle)
                            throws IOException
                    {
                        Map value = new LinkedHashMap();

                        Set<Map.Entry> entrySet = aMapping.entrySet();
                        for (Map.Entry entry : entrySet)
                        {
                            final Object itemKey = entry.getKey();
                            final Object itemValue = entry.getValue();
                            Node keyNode = getNodeCreatorFor(itemKey).toYamlNode(this);
                            Node valueNode = getNodeCreatorFor(itemValue).toYamlNode(this);
                            value.put(keyNode, valueNode);
                        }
                        return new MappingNode(aTag, value, aFlowStyle);
                    }
                };
            }
        };
    }

    /**
     * @return the yamlData
     */
    public Map<String, Object> getYamlData()
    {
        return mYamlData;
    }

    /**
     * @param aYamlData the yamlData to set
     */
    protected void setYamlData(Map<String, Object> aYamlData)
    {
        mYamlData = aYamlData;
    }
}
